﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace nr.Tombola.Cards
{
    /// <summary>
    /// Una cartella per il gioco della tombola.
    /// </summary>
    [Serializable()]
    public class Card : System.Runtime.Serialization.ISerializable
    {
        /// <summary>
        /// Nome assegnato alla cartella.
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Righe della cartella.
        /// </summary>
        /// <remarks>Si tratta di tre righe di cinque numeri.</remarks>
        private CardRow[] Rows { get; set; }

        /// <summary>
        /// Costruttore per la deserializzazione.
        /// </summary>
        /// <param name="info">Informazioni per la serializzazione.</param>
        /// <param name="context">Contesto per la serializzazione.</param>
        public Card(SerializationInfo info, StreamingContext context)
        {
            Name = info.GetString("name");
            Rows = (CardRow[])info.GetValue("rows", typeof(CardRow[]));
        }
        /// <summary>
        /// Costruttore.
        /// </summary>
        public Card() : this("Senza Nome", new CardRow[] { new CardRow(), new CardRow(), new CardRow() }) { }
        /// <summary>
        /// Costruttore.
        /// </summary>
        /// <param name="rows">Righe della cartella.</param>
        public Card(params CardRow[] rows) : this("Senza Nome", rows) { }
        /// <summary>
        /// Costruttore.
        /// </summary>
        /// <param name="name">Nome della cartella.</param>
        /// <param name="rows">Righe della cartella.</param>
        public Card(string name, params CardRow[] rows)
        {
            Name = name;

            if (rows.Length > 3)
                rows = rows.Take(3).ToArray();
            Rows = new CardRow[3];
            int index = 0;
            foreach (CardRow row in rows)
                Rows[index++] = row;
            for (int filler = index; filler < 3; ++filler) Rows[filler] = new CardRow();
        }
        /// <summary>
        /// Piazza un numero sulla cartella.
        /// </summary>
        /// <param name="number">Numero da piazzare.</param>
        /// <param name="rowIndex">Riga in cui piazzare il numero.</param>
        /// <returns>Restituisce vero (TRUE) se il numero può essere piazzato nella riga.</returns>
        public bool Place(int number, int rowIndex)
        {
            if ((rowIndex < 0) || (rowIndex > 2))
                throw new ArgumentOutOfRangeException("rowIndex", rowIndex, "The row index must value by 0 and 2.");
            return Rows[rowIndex].Place(number);
        }
        /// <summary>
        /// Piazza un numero sulla cartella.
        /// </summary>
        /// <param name="number">Numero da piazzare.</param>
        /// <returns>Restituisce vero (TRUE) se il numero può essere piazzato in qualche riga della cartella.</returns>
        public bool Place(int number)
        {
            int rowIndex = 0;
            while ((rowIndex < 3) && !Rows[rowIndex].Place(number)) ++rowIndex;
            return rowIndex < 3;
        }
        /// <summary>
        /// Elimina un numero da una riga della cartella.
        /// </summary>
        /// <param name="index">Indice del numero da eliminare.</param>
        /// <param name="rowIndex">Indice della riga su cui effettuare l'eliminazione.</param>
        public void Clear(int index, int rowIndex)
        {
            if ((rowIndex < 0) || (rowIndex > 2))
                throw new ArgumentOutOfRangeException("rowIndex", rowIndex, "The row index must value by 0 and 2.");
            Rows[rowIndex].Clear(index);
        }
        /// <summary>
        /// Accede ad una riga della cartella.
        /// </summary>
        /// <param name="rowIndex">Indice della riga richiesta.</param>
        /// <returns>Restituisce la riga della cartella.</returns>
        public CardRow this[int rowIndex]
        {
            get
            {
                if ((rowIndex < 0) || (rowIndex > 2))
                    throw new ArgumentOutOfRangeException("rowIndex", rowIndex, "The row index must value by 0 and 2.");
                return Rows[rowIndex];
            }
        }

        private void ReorderNumbers()
        {
            for (int dec = 0; dec < 9; ++dec)
            {
                for (int row = 0; row < 2; ++row)
                {
                    int number = this[row][dec];
                    for (int inner = row + 1; inner < 3; ++inner)
                    {
                        int comp = this[inner][dec];
                        if ((comp > 0) && (comp < number))
                        {
                            this[row].Clear(dec);
                            this[row].Place(comp);
                            this[inner].Clear(dec);
                            this[inner].Place(number);
                        }
                    }
                }
            }
        }

        public static Card Random(string name)
        {
            return Random(name, true);
        }

        /// <summary>
        /// Crea una cartella con numeri casuali al suo interno.
        /// </summary>
        /// <param name="name">Nome assegnato alla cartella.</param>
        /// <param name="reorderNumbers">Indica se i numeri sulla cartella devono essere riordinati per colonna in ordine crescente.</param>
        /// <returns>Restituisce la cartella casuale creata.</returns>
        public static Card Random(string name, bool reorderNumbers)
        {
            NumbersBag bag = new NumbersBag();
            bag.Shuffle();
            Card c = Random(bag);
            c.Name = name;

            if (reorderNumbers) c.ReorderNumbers();

            return c;
        }
        /// <summary>
        /// Crea una cartella casuale con i numeri presenti in un sacchetto.
        /// </summary>
        /// <param name="bag">Sacchetto da cui prelevare i numeri.</param>
        /// <returns>Restituisce la cartella casuale creata.</returns>
        private static Card Random(NumbersBag bag)
        {
            Card card = new Card();
            NumbersBag b = (NumbersBag)bag.Clone();
            for (int index = 0; index < 15; ++index)
            {
                b.MoveNext();
                int number = b.Current;
                int d = number / 10;
                if (number == 10) d = 0;
                if (number == 90) d = 8;
                while (!card.Place(number) && b.HasMoreNumbers)
                {
                    for (int n = d * 10; n < d * 10 + 9; ++n)
                        b.Pull(n);
                    if (b.MoveNext())
                        number = b.Current;
                }
            }
            return card;
        }

        private static void CreateLastCard(Card[] series, int[] numbers)
        {
            List<int> temp = new List<int>(numbers);
            Card card = new Card();
            int[] decs = new int[9];
            foreach (int number in numbers)
                ++decs[GetDec(number)];

            int firstdecinoverflow = -1;
            for (int index = 0; index < 8 && firstdecinoverflow < 0; ++index)
                if (decs[index] > 3) firstdecinoverflow = index;
            if (firstdecinoverflow > -1)
            {
                int start = firstdecinoverflow * 10;
                int end = start + 9;
                if (firstdecinoverflow == 0) { start = 1; end = 10; }
                else if (firstdecinoverflow == 1) { start = 11; }
                else if (firstdecinoverflow == 8) { end = 90; }
                int number = (from int n in numbers where n >= start && n <= end select n).First();
                int newNumber = 0;
                bool placed = false;
                for (int index = 0; index < 5 && !placed; ++index)
                    for (int row = 0; row < 3 && !placed; ++row)
                        if (series[index][row][firstdecinoverflow] == 0)
                        {
                            newNumber = (from int n in series[index][row].GetNumbers() where decs[GetDec(n)] > 3 select n).DefaultIfEmpty(0).FirstOrDefault();
                            if (newNumber > 0)
                            {
                                series[index][row].Replace(newNumber, number);
                                placed = true;
                            }
                        }

                if (placed)
                {
                    numbers[Array.IndexOf(numbers, number)] = newNumber;
                    CreateLastCard(series, numbers);
                }
            }
            else
            {
                List<int> priority = new List<int>();
                for (int index = 0; index < 9; ++index)
                    if (decs[index] == 3) priority.Insert(0, index);
                    else if (decs[index] == 2) priority.Insert(priority.DefaultIfEmpty(0).FirstOrDefault(x => x < 3), index);
                    else priority.Add(index);
                foreach (int index in priority)
                {
                    int start = index * 10;
                    int end = start + 9;
                    if (index == 0) { start = 1; end = 10; }
                    else if (index == 1) { start = 11; }
                    else if (index == 8) { end = 90; }
                    IEnumerable<int> selected = from int number in numbers where number >= start && number <= end select number;

                    foreach (int n in selected)
                        card.Place(n);
                }
            }
            series[5] = card;
        }

        public static int GetDec(int number)
        {
            int dec = number / 10;
            //if (number == 10) dec = 0;
            if (number == 90) dec = 8;
            return dec;
        }

        public static Card[] Series(string name)
        {
            return Series(name, true);
        }

        public static Card[] Series(string name, bool reorderNumbers)
        {
            Card[] result = new Card[6];
            NumbersBag bag = new NumbersBag();
            bag.Shuffle();
            // crea 5 cartelle casuali
            for (int count = 0; count < 5; ++count)
            {
                // crea una cartella casuale
                result[count] = Card.Random(bag);
                // elimina dal sacchetto i numeri inseriti nella cartella casuale creata
                for (int row = 0; row < 3; ++row)
                    for (int col = 0; col < 9; ++col)
                        bag.Pull(result[count][row][col]);
            }
            CreateLastCard(result, bag.ToArray());
            for (int index = 0; index < 6; ++index)
                result[index].Name = string.Format("{0}[{1}]", name, index + 1);

            if (reorderNumbers)
                foreach (Card c in result) c.ReorderNumbers();

            return result;
        }

        public override string ToString()
        {
            return Name;
        }

        #region ISerializable Members

        public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
        {
            info.AddValue("name", Name);
            info.AddValue("rows", Rows);
        }

        #endregion
    }
}
